/*-------------------------------------------------------------------------
 *
 *	 文件
 *		fe-misc.c
 *
 *	 描述
 *		 杂项有用函数
 *
 * 此处的通信例程类似于 backend/libpq/pqcomm.c 和 backend/libpq/pqformat.c 中的例程，但在前端 libpq 的相当不同的环境中运行。
 * 特别是，我们使用裸的非阻塞模式套接字，而不是 stdio 流，以避免不必要的应用程序阻塞。
 *
 * XXX: 将调试打印移动到更高层次。按目前的方式，阻塞和重启会导致重复打印。
 *
 * 我们必须使用与后端例程相同的传输数据表示。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-misc.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <signal.h>
#include <time.h>

#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#include <sys/time.h>
#endif

#ifdef HAVE_POLL_H
#include <poll.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include "libpq-fe.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
#include "pg_config_paths.h"
#include "port/pg_bswap.h"

static int	fc_pqPutMsgBytes(const void *fc_buf, size_t fc_len, PGconn *fc_conn);
static int	fc_pqSendSome(PGconn *fc_conn, int fc_len);
static int	fc_pqSocketCheck(PGconn *fc_conn, int fc_forRead, int fc_forWrite,
						  time_t fc_end_time);
static int	fc_pqSocketPoll(int fc_sock, int fc_forRead, int fc_forWrite, time_t fc_end_time);

/*
 * PQlibVersion: 返回 libpq 版本号
 */
int PQlibVersion(void)
{
	return PG_VERSION_NUM;
}


/*
 * pqGetc: 从连接获取1个字符
 *
 * 所有这些例程在成功时返回 0，在出错时返回 EOF。
 * 注意，对于 Get 例程，EOF 仅意味着缓冲区内数据不足，并不一定意味着存在严重错误。
 */
int pqGetc(char *fc_result, PGconn *fc_conn)
{
	if (fc_conn->inCursor >= fc_conn->inEnd)
		return EOF;

	*fc_result = fc_conn->inBuffer[fc_conn->inCursor++];

	return 0;
}


/*
 * pqPutc: 写入1个字符到当前消息
 */
int pqPutc(char fc_c, PGconn *fc_conn)
{
	if (fc_pqPutMsgBytes(&fc_c, 1, fc_conn))
		return EOF;

	return 0;
}


/*
 * pqGets[_append]:
 * 从连接获取一个以 null 结尾的字符串，
 * 并将其存储在可扩展的 PQExpBuffer 中。
 * 如果内存不足，仍然会读取整个字符串，
 * 但多余的字符会被静默丢弃。
 */
static int fc_pqGets_internal(PQExpBuffer fc_buf, PGconn *fc_conn, bool fc_resetbuffer)
{
	/* 将连接数据复制到局部变量以加快搜索循环 */
	char	   *fc_inBuffer = fc_conn->inBuffer;
	int			fc_inCursor = fc_conn->inCursor;
	int			fc_inEnd = fc_conn->inEnd;
	int			fc_slen;

	while (fc_inCursor < fc_inEnd && fc_inBuffer[fc_inCursor])
		fc_inCursor++;

	if (fc_inCursor >= fc_inEnd)
		return EOF;

	fc_slen = fc_inCursor - fc_conn->inCursor;

	if (fc_resetbuffer)
		resetPQExpBuffer(fc_buf);

	appendBinaryPQExpBuffer(fc_buf, fc_inBuffer + fc_conn->inCursor, fc_slen);

	fc_conn->inCursor = ++fc_inCursor;

	return 0;
}

int pqGets(PQExpBuffer fc_buf, PGconn *fc_conn)
{
	return fc_pqGets_internal(fc_buf, fc_conn, true);
}

int pqGets_append(PQExpBuffer fc_buf, PGconn *fc_conn)
{
	return fc_pqGets_internal(fc_buf, fc_conn, false);
}


/*
 * pqPuts: 将一个以 null 结尾的字符串写入当前消息
 */
int pqPuts(const char *fc_s, PGconn *fc_conn)
{
	if (fc_pqPutMsgBytes(fc_s, strlen(fc_s) + 1, fc_conn))
		return EOF;

	return 0;
}

/*
 * pqGetnchar:
 * 从缓冲区 s 中获取一个恰好 len 字节的字符串，无 null 结束符
 */
int pqGetnchar(char *fc_s, size_t fc_len, PGconn *fc_conn)
{
	if (fc_len > (size_t) (fc_conn->inEnd - fc_conn->inCursor))
		return EOF;

	memcpy(fc_s, fc_conn->inBuffer + fc_conn->inCursor, fc_len);
	/* 没有结束的 null */

	fc_conn->inCursor += fc_len;

	return 0;
}

/*
 * pqSkipnchar:
 * 跳过输入缓冲区中的 len 字节。
 *
 * 注意：这主要对其调试输出有用，应该与 pqGetnchar 的输出完全相同。
 * 我们假设相关数据将实际使用，但目前只是没有被复制到任何地方。
 */
int pqSkipnchar(size_t fc_len, PGconn *fc_conn)
{
	if (fc_len > (size_t) (fc_conn->inEnd - fc_conn->inCursor))
		return EOF;

	fc_conn->inCursor += fc_len;

	return 0;
}

/*
 * pqPutnchar:
 * 向当前消息写入恰好 len 字节
 */
int pqPutnchar(const char *fc_s, size_t fc_len, PGconn *fc_conn)
{
	if (fc_pqPutMsgBytes(fc_s, fc_len, fc_conn))
		return EOF;

	return 0;
}

/*
 * pqGetInt
 * 读取 2 或 4 字节的整数并从网络字节顺序转换为本地字节顺序
 */
int pqGetInt(int *fc_result, size_t fc_bytes, PGconn *fc_conn)
{
	uint16		fc_tmp2;
	uint32		fc_tmp4;

	switch (fc_bytes)
	{
		case 2:
			if (fc_conn->inCursor + 2 > fc_conn->inEnd)
				return EOF;
			memcpy(&fc_tmp2, fc_conn->inBuffer + fc_conn->inCursor, 2);
			fc_conn->inCursor += 2;
			*fc_result = (int) pg_ntoh16(fc_tmp2);
			break;
		case 4:
			if (fc_conn->inCursor + 4 > fc_conn->inEnd)
				return EOF;
			memcpy(&fc_tmp4, fc_conn->inBuffer + fc_conn->inCursor, 4);
			fc_conn->inCursor += 4;
			*fc_result = (int) pg_ntoh32(fc_tmp4);
			break;
		default:
			pqInternalNotice(&fc_conn->noticeHooks,
							 "integer of size %lu not supported by pqGetInt",
							 (unsigned long) fc_bytes);
			return EOF;
	}

	return 0;
}

/*
 * pqPutInt
 * 写入 2 或 4 字节的整数，将主机字节顺序转换为网络字节顺序。
 */
int pqPutInt(int fc_value, size_t fc_bytes, PGconn *fc_conn)
{
	uint16		fc_tmp2;
	uint32		fc_tmp4;

	switch (fc_bytes)
	{
		case 2:
			fc_tmp2 = pg_hton16((uint16) fc_value);
			if (fc_pqPutMsgBytes((const char *) &fc_tmp2, 2, fc_conn))
				return EOF;
			break;
		case 4:
			fc_tmp4 = pg_hton32((uint32) fc_value);
			if (fc_pqPutMsgBytes((const char *) &fc_tmp4, 4, fc_conn))
				return EOF;
			break;
		default:
			pqInternalNotice(&fc_conn->noticeHooks,
							 "integer of size %lu not supported by pqPutInt",
							 (unsigned long) fc_bytes);
			return EOF;
	}

	return 0;
}

/*
 * 确保连接的输出缓冲区可以容纳 bytes_needed 字节（调用者必须
 * 将已经存储的数据包含在值中！）
 *
 * 在成功时返回 0，如果扩展缓冲区失败则返回 EOF
 */
int pqCheckOutBufferSpace(size_t fc_bytes_needed, PGconn *fc_conn)
{
	int			fc_newsize = fc_conn->outBufSize;
	char	   *fc_newbuf;

	/* 如果我们有足够的空间则快速退出 */
	if (fc_bytes_needed <= (size_t) fc_newsize)
		return 0;

	/*
	 * 如果我们需要扩展缓冲区，首先尝试将其大小加倍；如果
	 * 这不奏效，则按 8K 的倍数扩展。这样可以避免通过反复的小幅扩展
	 * 来干扰 malloc 池。
	 *
	 * 注意：针对 newsize > 0 的测试是为了捕捉整数溢出。
	 */
	do
	{
		fc_newsize *= 2;
	} while (fc_newsize > 0 && fc_bytes_needed > (size_t) fc_newsize);

	if (fc_newsize > 0 && fc_bytes_needed <= (size_t) fc_newsize)
	{
		fc_newbuf = realloc(fc_conn->outBuffer, fc_newsize);
		if (fc_newbuf)
		{
			/* realloc 成功 */
			fc_conn->outBuffer = fc_newbuf;
			fc_conn->outBufSize = fc_newsize;
			return 0;
		}
	}

	fc_newsize = fc_conn->outBufSize;
	do
	{
		fc_newsize += 8192;
	} while (fc_newsize > 0 && fc_bytes_needed > (size_t) fc_newsize);

	if (fc_newsize > 0 && fc_bytes_needed <= (size_t) fc_newsize)
	{
		fc_newbuf = realloc(fc_conn->outBuffer, fc_newsize);
		if (fc_newbuf)
		{
			/* realloc 成功 */
			fc_conn->outBuffer = fc_newbuf;
			fc_conn->outBufSize = fc_newsize;
			return 0;
		}
	}

	/* realloc 失败。可能是内存不足 */
	appendPQExpBufferStr(&fc_conn->errorMessage,
						 "cannot allocate memory for output buffer\n");
	return EOF;
}

/*
 * 确保连接的输入缓冲区可以容纳 bytes_needed 字节（调用者必须
 * 将已经存储的数据包含在值中！）
 *
 * 在成功时返回 0，如果扩展缓冲区失败则返回 EOF
 */
int pqCheckInBufferSpace(size_t fc_bytes_needed, PGconn *fc_conn)
{
	int			fc_newsize = fc_conn->inBufSize;
	char	   *fc_newbuf;

	/* 如果我们有足够的空间则快速退出 */
	if (fc_bytes_needed <= (size_t) fc_newsize)
		return 0;

	/*
	 * 在得出我们需要扩展缓冲区之前，将其中的内容左对齐
	 * 并重新检查。调用者的 bytes_needed 值
	 * 包括 inStart 左侧的任何数据，但我们可以删除这些数据
	 * 而不是扩展缓冲区。让这个功能做这件事会稍显奇怪，
	 * 但总比让调用者担心要好。
	 */
	fc_bytes_needed -= fc_conn->inStart;

	if (fc_conn->inStart < fc_conn->inEnd)
	{
		if (fc_conn->inStart > 0)
		{
			memmove(fc_conn->inBuffer, fc_conn->inBuffer + fc_conn->inStart,
					fc_conn->inEnd - fc_conn->inStart);
			fc_conn->inEnd -= fc_conn->inStart;
			fc_conn->inCursor -= fc_conn->inStart;
			fc_conn->inStart = 0;
		}
	}
	else
	{
		/* 缓冲区在逻辑上是空的，重置它 */
		fc_conn->inStart = fc_conn->inCursor = fc_conn->inEnd = 0;
	}

	/* 重新检查我们是否有足够的空间 */
	if (fc_bytes_needed <= (size_t) fc_newsize)
		return 0;

	/*
	 * 如果我们需要扩展缓冲区，首先尝试将其大小加倍；如果
	 * 这不奏效，则按 8K 的倍数扩展。这样可以避免通过反复的小幅扩展
	 * 来干扰 malloc 池。
	 *
	 * 注意：针对 newsize > 0 的测试是为了捕捉整数溢出。
	 */
	do
	{
		fc_newsize *= 2;
	} while (fc_newsize > 0 && fc_bytes_needed > (size_t) fc_newsize);

	if (fc_newsize > 0 && fc_bytes_needed <= (size_t) fc_newsize)
	{
		fc_newbuf = realloc(fc_conn->inBuffer, fc_newsize);
		if (fc_newbuf)
		{
			/* realloc 成功 */
			fc_conn->inBuffer = fc_newbuf;
			fc_conn->inBufSize = fc_newsize;
			return 0;
		}
	}

	fc_newsize = fc_conn->inBufSize;
	do
	{
		fc_newsize += 8192;
	} while (fc_newsize > 0 && fc_bytes_needed > (size_t) fc_newsize);

	if (fc_newsize > 0 && fc_bytes_needed <= (size_t) fc_newsize)
	{
		fc_newbuf = realloc(fc_conn->inBuffer, fc_newsize);
		if (fc_newbuf)
		{
			/* realloc 成功 */
			fc_conn->inBuffer = fc_newbuf;
			fc_conn->inBufSize = fc_newsize;
			return 0;
		}
	}

	/* realloc 失败。可能是内存不足 */
	appendPQExpBufferStr(&fc_conn->errorMessage,
						 "cannot allocate memory for input buffer\n");
	return EOF;
}

/*
 * pqPutMsgStart：开始构造发送给服务器的消息
 *
 * msg_type是消息类型字节，0表示没有类型字节的消息
 * （只有启动消息没有类型字节）
 *
 * 成功时返回0，错误时返回EOF
 *
 * 这里的思路是我们在conn->outBuffer中构造消息，
 * 从outBuffer中任何已存在的数据之后开始（即，
 * 在outBuffer+outCount处）。我们根据需要扩大缓冲区以容纳消息。
 * 当消息完成时，我们填充长度字（如果需要），
 * 然后将outCount推进消息之后，使其可以发送。
 *
 * 状态变量conn->outMsgStart指向不完整消息的
 * 长度字：根据是否存在类型字节，它可以是outCount
 * 或outCount+1。状态变量conn->outMsgEnd是到目前为止
 * 收集的数据的结尾。
 */
int pqPutMsgStart(char fc_msg_type, PGconn *fc_conn)
{
	int			fc_lenPos;
	int			fc_endPos;

	/* 为消息类型字节留出空间 */
	if (fc_msg_type)
		fc_endPos = fc_conn->outCount + 1;
	else
		fc_endPos = fc_conn->outCount;

	/* 我们需要长度字吗？ */
	fc_lenPos = fc_endPos;
	/* 为消息长度留出空间 */
	fc_endPos += 4;

	/* 确保为消息头留出空间 */
	if (pqCheckOutBufferSpace(fc_endPos, fc_conn))
		return EOF;
	/* 好吧，如果有的话，保存消息类型字节 */
	if (fc_msg_type)
		fc_conn->outBuffer[fc_conn->outCount] = fc_msg_type;
	/* 设置消息指针 */
	fc_conn->outMsgStart = fc_lenPos;
	fc_conn->outMsgEnd = fc_endPos;
	/* 长度字（如果需要）将由pqPutMsgEnd填充 */

	return 0;
}

/*
 * pqPutMsgBytes：向部分构造的消息中添加字节
 *
 * 成功时返回0，错误时返回EOF
 */
static int fc_pqPutMsgBytes(const void *fc_buf, size_t fc_len, PGconn *fc_conn)
{
	/* 确保有足够的空间 */
	if (pqCheckOutBufferSpace(fc_conn->outMsgEnd + fc_len, fc_conn))
		return EOF;
	/* 好吧，保存数据 */
	memcpy(fc_conn->outBuffer + fc_conn->outMsgEnd, fc_buf, fc_len);
	fc_conn->outMsgEnd += fc_len;
	/* 此处没有Pfdebug调用，调用者应自行处理 */
	return 0;
}

/*
 * pqPutMsgEnd：完成构造消息并可能发送它
 *
 * 成功时返回0，错误时返回EOF
 *
 * 除非我们累计至少8K的数据（Unix系统上管道缓冲区的典型大小），
 * 否则我们在这里实际上不会发送任何内容。这避免了发送小的
 * 部分数据包。当重要的是将所有数据刷新到服务器时，调用者
 * 必须使用pqFlush。
 */
int pqPutMsgEnd(PGconn *fc_conn)
{
	/* 如果需要，填写长度字 */
	if (fc_conn->outMsgStart >= 0)
	{
		uint32		fc_msgLen = fc_conn->outMsgEnd - fc_conn->outMsgStart;

		fc_msgLen = pg_hton32(fc_msgLen);
		memcpy(fc_conn->outBuffer + fc_conn->outMsgStart, &fc_msgLen, 4);
	}

	/* 跟踪客户端到服务器的消息 */
	if (fc_conn->Pfdebug)
	{
		if (fc_conn->outCount < fc_conn->outMsgStart)
			pqTraceOutputMessage(fc_conn, fc_conn->outBuffer + fc_conn->outCount, true);
		else
			pqTraceOutputNoTypeByteMessage(fc_conn,
										   fc_conn->outBuffer + fc_conn->outMsgStart);
	}

	/* 使消息符合发送条件 */
	fc_conn->outCount = fc_conn->outMsgEnd;

	if (fc_conn->outCount >= 8192)
	{
		int			fc_toSend = fc_conn->outCount - (fc_conn->outCount % 8192);

		if (fc_pqSendSome(fc_conn, fc_toSend) < 0)
			return EOF;
		/* 在非阻塞模式下，如果无法全部发送，不要抱怨 */
	}

	return 0;
}

/* ----------
 * pqReadData：读取更多数据，如果有可用数据
 * 可能的返回值：
 *	 1：成功加载至少一个字节
 *	 0：当前没有可用数据，但没有检测到错误
 *	-1：检测到错误（包括EOF = 连接关闭）；
 *		conn->errorMessage已设置
 * 注意：调用者不能假设指针或索引在conn->inBuffer
 * 中在此调用期间保持有效！
 * ----------
 */
int pqReadData(PGconn *fc_conn)
{
	int			fc_someread = 0;
	int			fc_nread;

	if (fc_conn->sock == PGINVALID_SOCKET)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("connection not open\n"));
		return -1;
	}

	/* 左对齐缓冲区中的任何数据以腾出空间 */
	if (fc_conn->inStart < fc_conn->inEnd)
	{
		if (fc_conn->inStart > 0)
		{
			memmove(fc_conn->inBuffer, fc_conn->inBuffer + fc_conn->inStart,
					fc_conn->inEnd - fc_conn->inStart);
			fc_conn->inEnd -= fc_conn->inStart;
			fc_conn->inCursor -= fc_conn->inStart;
			fc_conn->inStart = 0;
		}
	}
	else
	{
		/* 缓冲区在逻辑上是空的，重置它 */
		fc_conn->inStart = fc_conn->inCursor = fc_conn->inEnd = 0;
	}

	/*
	 * 如果缓冲区相当满，扩展它。我们需要能够扩展缓冲区，以防单个消息超过初始缓冲区大小。我们在完全填充缓冲区之前扩展它，以避免向内核请求部分包。这里的魔法常量应该足够大，以容纳一个 TCP 包或 Unix 管道缓冲区负载。8K 是通常的管道缓冲区大小，因此...
	 */
	if (fc_conn->inBufSize - fc_conn->inEnd < 8192)
	{
		if (pqCheckInBufferSpace(fc_conn->inEnd + (size_t) 8192, fc_conn))
		{
			/*
			 * 我们不坚持扩展成功，但我们需要一些空间
			 */
			if (fc_conn->inBufSize - fc_conn->inEnd < 100)
				return -1;		/* errorMessage 已经设置 */
		}
	}

	/* 好的，尝试读取一些数据 */
retry3:
	fc_nread = pqsecure_read(fc_conn, fc_conn->inBuffer + fc_conn->inEnd,
						  fc_conn->inBufSize - fc_conn->inEnd);
	if (fc_nread < 0)
	{
		switch (SOCK_ERRNO)
		{
			case EINTR:
				goto retry3;

				/* 一些系统返回 EAGAIN/EWOULDBLOCK 表示没有数据 */
#ifdef EAGAIN
			case EAGAIN:
				return fc_someread;
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
			case EWOULDBLOCK:
				return someread;
#endif

				/* 如果连接失败，我们可能会在这里得到 ECONNRESET 等错误 */
			case ALL_CONNECTION_FAILURE_ERRNOS:
				goto definitelyFailed;

			default:
				/* pqsecure_read 为我们设置了错误消息 */
				return -1;
		}
	}
	if (fc_nread > 0)
	{
		fc_conn->inEnd += fc_nread;

		/*
		 * 针对某些内核仅在 recv() 调用中给我们返回
		 * 1 个数据包的事实的黑客处理，尽管我们请求了更多且还有更多可用数据。 如果看起来我们正在读取一个长消息，
		 * 立即循环回 recv()，直到我们耗尽数据或缓冲区空间。 
		 * 如果没有这个，libpq 的更高层的阻塞和重启行为会导致在长消息上的 O(N^2) 性能。
		 *
		 * 由于我们将数据左对齐，conn->inEnd 给出了
		 * 当前消息中已经读取的数据量。我们认为
		 * 消息“长” 一旦我们获得 32k ...
		 */
		if (fc_conn->inEnd > 32768 &&
			(fc_conn->inBufSize - fc_conn->inEnd) >= 8192)
		{
			fc_someread = 1;
			goto retry3;
		}
		return 1;
	}

	if (fc_someread)
		return 1;				/* 成功尝试后读取为零 */

	/*
	 * 返回值为 0 可能意味着现在没有数据可用，或者
	 * 可能意味着 EOF —— 也就是说，服务器已关闭连接。
	 * 由于我们将套接字设置为非阻塞模式，区分这两者的唯一方法
	 * 是查看 select() 是否在说文件已准备好。
	 * 可怜。不过，幸运的是，我们不希望这个路径被频繁调用，
	 * 因为在正常实践中，我们不应该尝试读取数据，除非
	 * 文件已经选择用于读取。
	 *
	 * 在 SSL 模式下更糟糕：SSL_read() 可能会说 WANT_READ，然后
	 * 数据可能在我们进行 pqReadReady() 测试之前到达，但第二个
	 * SSL_read() 仍然可能说 WANT_READ，因为接收到的数据不是
	 * 完整的 SSL 记录。因此我们必须装傻，假设还有更多
	 * 数据，依赖 SSL 层检测真实的 EOF。
	 */

#ifdef USE_SSL
	if (fc_conn->ssl_in_use)
		return 0;
#endif

	switch (pqReadReady(fc_conn))
	{
		case 0:
			/* 确定没有可用数据 */
			return 0;
		case 1:
			/* 准备读取 */
			break;
		default:
			/* 我们用更有用的内容覆盖 pqReadReady 的消息 */
			goto definitelyEOF;
	}

	/*
	 * 仍然不确定这是否是 EOF，因为一些数据可能刚到达。
	 */
retry4:
	fc_nread = pqsecure_read(fc_conn, fc_conn->inBuffer + fc_conn->inEnd,
						  fc_conn->inBufSize - fc_conn->inEnd);
	if (fc_nread < 0)
	{
		switch (SOCK_ERRNO)
		{
			case EINTR:
				goto retry4;

				/* 一些系统返回 EAGAIN/EWOULDBLOCK 表示没有数据 */
#ifdef EAGAIN
			case EAGAIN:
				return 0;
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
			case EWOULDBLOCK:
				return 0;
#endif

				/* 如果连接失败，我们可能会在这里得到 ECONNRESET 等错误 */
			case ALL_CONNECTION_FAILURE_ERRNOS:
				goto definitelyFailed;

			default:
				/* pqsecure_read 为我们设置了错误消息 */
				return -1;
		}
	}
	if (fc_nread > 0)
	{
		fc_conn->inEnd += fc_nread;
		return 1;
	}

	/*
	 * 好的，尽管 select() 说准备好了，但我们正在读取零。这
	 * 意味着连接已关闭。处理一下。
	 */
definitelyEOF:
	appendPQExpBufferStr(&fc_conn->errorMessage,
						 libpq_gettext("server closed the connection unexpectedly\n"
									   "\tThis probably means the server terminated abnormally\n"
									   "\tbefore or while processing the request.\n"));

	/* 如果低级代码已经设置了合适的 errorMessage，则来这里 */
definitelyFailed:
	/* 不要丢弃任何已读取的数据；调用者仍然想要它 */
	pqDropConnection(fc_conn, false);
	fc_conn->status = CONNECTION_BAD;	/* 不再与后端连接 */
	return -1;
}

/*
			 * 我们没有发送所有数据，等到我们能够发送更多时再发送。
			 *
			 * 有一些情况，我们无法发送数据，因为通信通道已满，但我们不能期望服务器最终清理通道，因为它被阻塞，试图发送数据给我们。  （这可能发生在我们发送大量COPY数据时，服务器生成了很多NOTICE响应。）为了避免死锁情况，我们必须准备好接受和缓冲即将到来的数据，然后再尝试。 此外，这些即将到来的数据可能在我们进入睡眠状态后才会到达。 因此，我们等待读取就绪或写入就绪。
			 *
			 * 在非阻塞模式下，我们不会直接在此处等待，而是返回1以指示数据仍在等待中。调用者应该等待读取和写入就绪条件，并在读取就绪时调用PQconsumeInput()，但以防它不这样做，我们在返回之前自己调用pqReadData()。如果数据尚未到达，这还不够，但这是我们能做的最好事情，在实践中效果相当不错。 （文档曾经说过你只需要等待写就绪，因此仍然有很多这样的应用程序存在。）
			 *
			 * 请注意，这里的错误不会导致write_failed被设置。
			 */
static int fc_pqSendSome(PGconn *fc_conn, int fc_len)
{
	char	   *fc_ptr = fc_conn->outBuffer;
	int			fc_remaining = fc_conn->outCount;
	int			fc_result = 0;

	/* 错误消息已经设置 */
	if (fc_conn->write_failed)
	{
		/* 移动缓冲区中剩余内容 */
		fc_conn->outCount = 0;
		/*
 * pqFlush: 发送输出缓冲区中等待的数据
 *
 * 成功时返回0，失败时返回-1，当不是所有数据都能发送因为套接字会阻塞且连接是非阻塞时返回1。
 * （参见pqSendSome关于如何处理失败的注释。）
 */
		if (fc_conn->sock != PGINVALID_SOCKET)
		{
			if (pqReadData(fc_conn) < 0)
				return -1;
		}
		return 0;
	}

	if (fc_conn->sock == PGINVALID_SOCKET)
	{
		fc_conn->write_failed = true;
		/*
 * pqWait: 等待直到我们可以读取或写入连接套接字
 *
 * JAB: 如果启用并使用SSL并且用于读取，缓冲的字节会短路调用select()。
 *
 * 如果内核在套接字上标记了异常条件，我们也会停止等待并返回。 实际的错误条件将在调用者尝试读取或写入套接字时被检测并报告。
 */
		/*
 * pqWaitTimed: 等待，但不超过finish_time。
 *
 * finish_time = ((time_t) -1) 禁用等待限制。
 *
 * 失败时返回-1，如果套接字可读/可写则返回0，如果超时则返回1。
 */
		fc_conn->write_err_msg = strdup(libpq_gettext("connection not open\n"));
		/* errorMessage已经设置 */
		fc_conn->outCount = 0;
		return 0;
	}

	/*
 * pqReadReady: select()是否表示文件准备好读取？
 * 失败时返回-1，如果不准备好返回0，如果准备好返回1。
 */
	while (fc_len > 0)
	{
		int			fc_sent;

#ifndef WIN32
		fc_sent = pqsecure_write(fc_conn, fc_ptr, fc_len);
#else

		/*
 * pqWriteReady: select()是否表示文件准备好写入？
 * 失败时返回-1，如果不准备好返回0，如果准备好返回1。
 */
		fc_sent = pqsecure_write(conn, ptr, Min(len, 65536));
#endif

		if (fc_sent < 0)
		{
			/*
 * 检查套接字，使用poll或select来查看是否有数据可以读取、写入或两者兼有。 如果满足一个或多个条件，返回>0；如果超时返回0；如果发生错误返回-1。
 *
 * 如果在使用SSL，SSL缓冲区会在直接检查套接字的读取数据之前进行检查。
 */
			switch (SOCK_ERRNO)
			{
#ifdef EAGAIN
				case EAGAIN:
					break;
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
				case EWOULDBLOCK:
					break;
#endif
				case EINTR:
					continue;

				default:
					/* errorMessage已经设置 */
					fc_conn->outCount = 0;

					/*
 * pqFlush: 发送输出缓冲区中等待的数据
 *
 * 成功时返回0，失败时返回-1，当不是所有数据都能发送因为套接字会阻塞且连接是非阻塞时返回1。
 * （参见pqSendSome关于如何处理失败的注释。）
 */
					if (fc_conn->sock != PGINVALID_SOCKET)
					{
						if (pqReadData(fc_conn) < 0)
							return -1;
					}

					/* 只要我们得到 EINTR，就会重试 */
					if (fc_conn->write_failed)
						return 0;
					else
						return -1;
			}
		}
		else
		{
			fc_ptr += fc_sent;
			fc_len -= fc_sent;
			fc_remaining -= fc_sent;
		}

		if (fc_len > 0)
		{
			/*
 * 检查文件描述符是否有可读和/或可写数据，可能会等待。
 * 如果 neither forRead nor forWrite 被设置，立即返回超时
 * 条件（不等待）。如果条件满足返回 >0，如果发生超时返回 0，
 * 如果发生错误或中断返回 -1。
 *
 * 如果 end_time 为 -1，超时将是无限的。如果 end_time 为 0
 * （或实际上是任何在当前时间之前的时间），超时将立即发生（无阻塞）。
 */
			if (pqReadData(fc_conn) < 0)
			{
				fc_result = -1;	/* 如果可用，我们使用 poll(2)，否则使用 select(2) */
				break;
			}

			if (pqIsnonblocking(fc_conn))
			{
				fc_result = 1;
				break;
			}

			if (pqWait(true, true, fc_conn))
			{
				fc_result = -1;
				break;
			}
		}
	}

	/* 计算适当的超时间隔 */
	if (fc_remaining > 0)
		memmove(fc_conn->outBuffer, fc_ptr, fc_remaining);
	fc_conn->outCount = fc_remaining;

	return fc_result;
}


/* !HAVE_POLL */
int pqFlush(PGconn *fc_conn)
{
	if (fc_conn->outCount > 0)
	{
		if (fc_conn->Pfdebug)
			fflush(fc_conn->Pfdebug);

		return fc_pqSendSome(fc_conn, fc_conn->outCount);
	}

	return 0;
}


/* 计算适当的超时间隔 */
int pqWait(int fc_forRead, int fc_forWrite, PGconn *fc_conn)
{
	return pqWaitTimed(fc_forRead, fc_forWrite, fc_conn, (time_t) -1);
}

/* HAVE_POLL */
int pqWaitTimed(int fc_forRead, int fc_forWrite, PGconn *fc_conn, time_t fc_finish_time)
{
	int			fc_result;

	fc_result = fc_pqSocketCheck(fc_conn, fc_forRead, fc_forWrite, fc_finish_time);

	if (fc_result < 0)
		return -1;				/* errorMessage已经设置 */

	if (fc_result == 0)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("timeout expired\n"));
		return 1;
	}

	return 0;
}

/*
 * 返回从 s 开始的字符的字节长度，使用
 * 指定的编码。
 *
 * 注意：当处理不确定在指定编码中有效的文本时，
 * 结果可能超出实际剩余字符串的长度。
 * 不准备处理该情况的调用者应该使用 PQmblenBounded()。
 */
int pqReadReady(PGconn *fc_conn)
{
	return fc_pqSocketCheck(fc_conn, 1, 0, (time_t) 0);
}

/*
 * 返回从 s 开始的字符的字节长度，使用
 * 指定的编码；但不超过字符串的末尾距离。
 */
int pqWriteReady(PGconn *fc_conn)
{
	return fc_pqSocketCheck(fc_conn, 0, 1, (time_t) 0);
}

/*
 * 返回从 s 开始的字符的显示长度，使用
 * 指定的编码。
 */
static int fc_pqSocketCheck(PGconn *fc_conn, int fc_forRead, int fc_forWrite, time_t fc_end_time)
{
	int			fc_result;

	if (!fc_conn)
		return -1;
	if (fc_conn->sock == PGINVALID_SOCKET)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("invalid socket\n"));
		return -1;
	}

#ifdef USE_SSL
	/*
 * 从环境变量 PGCLIENTENCODING 获取编码 ID。
 */
	if (fc_forRead && fc_conn->ssl_in_use && pgtls_read_pending(fc_conn))
	{
		/*
	 * 至少在 Windows 上，如果多个线程同时调用 bindtextdomain()，
	 * 一些 gettext 实现会失败。使用互斥锁和标志变量确保
	 * 每个进程仅调用一次。尚不清楚非 Windows 平台上是否存在类似的错误，
	 * 但我们可以在任何地方都采用相同的方式。
	 */
		return 1;
	}
#endif

	/* bindtextdomain() 不会保留 errno */
	do
		fc_result = fc_pqSocketPoll(fc_conn->sock, fc_forRead, fc_forWrite, fc_end_time);
	while (fc_result < 0 && SOCK_ERRNO == EINTR);

	if (fc_result < 0)
	{
		char		fc_sebuf[PG_STRERROR_R_BUFLEN];

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("%s() failed: %s\n"),
						  "select",
						  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
	}

	return fc_result;
}


/*
			 * 此处没有可重定位查找，因为调用可执行文件可能
			 * 在任何地方
			 */
static int fc_pqSocketPoll(int fc_sock, int fc_forRead, int fc_forWrite, time_t fc_end_time)
{
	/* 启用NLS */
#ifdef HAVE_POLL
	struct pollfd fc_input_fd;
	int			fc_timeout_ms;

	if (!fc_forRead && !fc_forWrite)
		return 0;

	fc_input_fd.fd = fc_sock;
	fc_input_fd.events = POLLERR;
	fc_input_fd.revents = 0;

	if (fc_forRead)
		fc_input_fd.events |= POLLIN;
	if (fc_forWrite)
		fc_input_fd.events |= POLLOUT;

	/* Compute appropriate timeout interval */
	if (fc_end_time == ((time_t) -1))
		fc_timeout_ms = -1;
	else
	{
		time_t		fc_now = time(NULL);

		if (fc_end_time > fc_now)
			fc_timeout_ms = (fc_end_time - fc_now) * 1000;
		else
			fc_timeout_ms = 0;
	}

	return poll(&fc_input_fd, 1, fc_timeout_ms);
#else							/* !HAVE_POLL */

	fd_set		fc_input_mask;
	fd_set		fc_output_mask;
	fd_set		fc_except_mask;
	struct timeval fc_timeout;
	struct timeval *fc_ptr_timeout;

	if (!fc_forRead && !fc_forWrite)
		return 0;

	FD_ZERO(&fc_input_mask);
	FD_ZERO(&fc_output_mask);
	FD_ZERO(&fc_except_mask);
	if (fc_forRead)
		FD_SET(fc_sock, &fc_input_mask);

	if (forWrite)
		FD_SET(fc_sock, &fc_output_mask);
	FD_SET(fc_sock, &fc_except_mask);

	/* Compute appropriate timeout interval */
	if (fc_end_time == ((time_t) -1))
		fc_ptr_timeout = NULL;
	else
	{
		time_t		now = time(NULL);

		if (fc_end_time > now)
			fc_timeout.tv_sec = fc_end_time - now;
		else
			fc_timeout.tv_sec = 0;
		fc_timeout.tv_usec = 0;
		fc_ptr_timeout = &fc_timeout;
	}

	return select(fc_sock + 1, &fc_input_mask, &fc_output_mask,
				  &fc_except_mask, fc_ptr_timeout);
#endif							/* HAVE_POLL */
}


/*
 * A couple of "miscellaneous" multibyte related functions. They used
 * to be in fe-print.c but that file is doomed.
 */

/*
 * Returns the byte length of the character beginning at s, using the
 * specified encoding.
 *
 * Caution: when dealing with text that is not certainly valid in the
 * specified encoding, the result may exceed the actual remaining
 * string length.  Callers that are not prepared to deal with that
 * should use PQmblenBounded() instead.
 */
int PQmblen(const char *fc_s, int fc_encoding)
{
	return pg_encoding_mblen(fc_encoding, fc_s);
}

/*
 * Returns the byte length of the character beginning at s, using the
 * specified encoding; but not more than the distance to end of string.
 */
int PQmblenBounded(const char *fc_s, int fc_encoding)
{
	return strnlen(fc_s, pg_encoding_mblen(fc_encoding, fc_s));
}

/*
 * Returns the display length of the character beginning at s, using the
 * specified encoding.
 */
int PQdsplen(const char *fc_s, int fc_encoding)
{
	return pg_encoding_dsplen(fc_encoding, fc_s);
}

/*
 * Get encoding id from environment variable PGCLIENTENCODING.
 */
int PQenv2encoding(void)
{
	char	   *fc_str;
	int			fc_encoding = PG_SQL_ASCII;

	fc_str = getenv("PGCLIENTENCODING");
	if (fc_str && *fc_str != '\0')
	{
		fc_encoding = pg_char_to_encoding(fc_str);
		if (fc_encoding < 0)
			fc_encoding = PG_SQL_ASCII;
	}
	return fc_encoding;
}


#ifdef ENABLE_NLS

static void libpq_binddomain(void)
{
	/*
	 * 至少在Windows上，有些gettext实现如果
	 * 多个线程同时调用bindtextdomain()会失败。使用互斥锁和
	 * 标志变量来确保我们每个进程只调用一次。尚不清楚
	 * 在非Windows平台上是否存在类似的bug，但我们
	 * 可能希望在各处都这样做。
	 */
	static volatile bool already_bound = false;
	static pthread_mutex_t binddomain_mutex = PTHREAD_MUTEX_INITIALIZER;

	if (!already_bound)
	{
		/* bindtextdomain() does not preserve errno */
#ifdef WIN32
		int			save_errno = GetLastError();
#else
		int			save_errno = errno;
#endif

		(void) pthread_mutex_lock(&binddomain_mutex);

		if (!already_bound)
		{
			const char *ldir;

			/*
			 * 这里没有可重定位查找，因为调用的可执行文件可能
			 * 在任何地方
			 */
			ldir = getenv("PGLOCALEDIR");
			if (!ldir)
				ldir = LOCALEDIR;
			bindtextdomain(PG_TEXTDOMAIN("libpq"), ldir);
			already_bound = true;
		}

		(void) pthread_mutex_unlock(&binddomain_mutex);

#ifdef WIN32
		SetLastError(save_errno);
#else
		errno = save_errno;
#endif
	}
}

char * libpq_gettext(const char *msgid)
{
	libpq_binddomain();
	return dgettext(PG_TEXTDOMAIN("libpq"), msgid);
}

char * libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
{
	libpq_binddomain();
	return dngettext(PG_TEXTDOMAIN("libpq"), msgid, msgid_plural, n);
}

#endif							/* 启用NLS */
